#include "obj3.h"

namespace GeorgeLib
{

	// constructor : no dimension values
	obj3::obj3(void) :
		l(0), h(0), w(0), r(0), bCollide(false) {}

	// constructor : object with sphere dimension
	obj3::obj3(float R) :
		l(0), h(0), w(0), r(R), bCollide(true) {}

	// constructor : object with box dimensions
	obj3::obj3(float L, float H, float W) :
		l(L), h(H), w(W), r(0), bCollide(true) {}

	// constructor : object with sphere and box dimensions
	obj3::obj3(float L, float H, float W, float R) :
		l(L), h(H), w(W), r(R), bCollide(true) {}

	// constructor : from another object
	obj3::obj3(obj3 &obj) :
		l(obj.l), h(obj.h), w(obj.w), r(obj.r), bCollide(obj.bCollide) {}

	// destructor
	obj3::~obj3(void)	{}

	// get the minimum coordinates of the bounding box
	vec3 obj3::getmin(void)
	{
		return vec3(-l / 2, -h / 2, -w / 2);
	}

	// get the maximum coordinates of the bounding box
	vec3 obj3::getmax(void)
	{
		return vec3(l / 2, h / 2, w / 2);
	}

#pragma region external functions
	bool boxcontainbox(vec3 thismin, vec3 thismax, vec3 thatmin, vec3 thatmax)
	{
		// thismin		: minimum coordinates of container
		// thismax		: maximum coordinates of container
		// thatmin		: minimum coordinates of object to be contained
		// thatmax		: maximum coordinates of object to be contained
		return  thatmin.x >= thismax.x && thatmax.x <= thismin.x &&
			thatmin.y >= thismax.y && thatmax.y <= thismin.y &&
			thatmin.z >= thismax.z && thatmax.z <= thismin.z;
	}

	bool boxcontainsphere(vec3 min, vec3 max, vec3 spherepos, float radius)
	{
		// min			: minimum coordinates of box
		// max			: maximum coordinates of box
		// spherepos	: position coordinates of sphere
		// radius		: radius of sphere
		return	spherepos.x - radius >= min.x && spherepos.x + radius <= max.x &&
			spherepos.y - radius >= min.y && spherepos.y + radius <= max.y &&
			spherepos.z - radius >= min.z && spherepos.z + radius <= max.z;
	}

	bool spherecontainbox(vec3 min, vec3 max, vec3 spherepos, float radius)
	{
		// min			: minimum coordinates of box
		// max			: maximum coordinates of box
		// spherepos	: position coordinates of sphere
		// radius		: radius of sphere
		return	spherepos.x - radius <= min.x && spherepos.x + radius >= max.x &&
			spherepos.y - radius <= min.y && spherepos.y + radius >= max.y &&
			spherepos.z - radius <= min.z && spherepos.z + radius >= max.z;
	}

	bool boxcollidebox(vec3 thismin, vec3 thismax, vec3 thatmin, vec3 thatmax)
	{
		// thismin		: minimum coordinates of container
		// thismax		: maximum coordinates of container
		// thatmin		: minimum coordinates of object to be contained
		// thatmax		: maximum coordinates of object to be contained
		return	thatmin.x >= thismax.x || thatmax.x <= thismin.x ||
			thatmin.y >= thismax.y || thatmax.y <= thismin.y ||
			thatmin.z >= thismax.z || thatmax.z <= thismin.z;
	}

#define MAX(a,b) ((a>b)?a:b)

	bool boxcollidesphere(vec3 min, vec3 max, vec3 spherepos, float radius)
	{
		// min			: minimum coordinates of box
		// max			: maximum coordinates of box
		// spherepos	: position coordinates of sphere
		// radius		: radius of sphere
		float d = 0, e = 0;

		for (int i = 0; i < 3; ++i)
		{
			e = MAX(min[i] - spherepos[i], 0.0f) + MAX(spherepos[i] - max[i], 0.0f);
			if (e <= radius) break;
			d = d + e*e;
		}
		if (d <= radius * radius) return true;
		return false;
	}
#pragma endregion

	bool obj3::checkcontain(obj3 obj)
	{
		// obj passed in is the object itself
		if (&obj == this)
		{
			Logger->AddLog("obj3::checkcontain(obj3&) : Error checking contain on self, returning false");
			return false;
		}

		// either object doesn't detect collision
		if (!bCollide || !obj.bCollide) return false;

		// this object is smaller than obj passed in
		if (l < obj.l || h < obj.h || w < obj.w) return false;

		// sphere-sphere contain
		if (r != 0 && obj.r != 0)
		{
			return	(pos - obj.pos).magnitude() <= r;
		}

		// box-box contain
		if (r == 0 && obj.r == 0)
		{
			return boxcontainbox(getmin(), getmax(), obj.getmin(), obj.getmax());
		}

		// sphere-box contain
		if (r == 0 && obj.r != 0)
		{
			return spherecontainbox(obj.getmin(), obj.getmax(), pos, r);
		}

		// box-sphere contain
		if (r != 0 && obj.r == 0)
		{
			return boxcontainsphere(getmin(), getmax(), obj.pos, obj.r);
		}

		return false;
	}

	bool obj3::checkcontain(vec3 vec)
	{
		vec3 min = getmin(); vec3 max = getmax();
		return	pos.x > min.x && pos.x < max.x &&
			pos.y > min.y && pos.y < max.y &&
			pos.z > min.z && pos.z < max.z;
	}

	bool obj3::checkcollide(obj3 obj)
	{
		// obj passed in is the object itself
		if (&obj == this)
		{
			Logger->AddLog("obj3::checkcollide(obj3&) : Error checking contain on self, returning false");
			return false;
		}

		// either object doesn't detect collision
		if (!bCollide || !obj.bCollide) return false;

		// sphere-sphere collision
		if (r != 0 && obj.r != 0)
		{
			return (pos - obj.pos).magnitude() < r + obj.r;
		}

		// box-box collision
		if (r == 0 && obj.r == 0)
		{
			return boxcollidebox(getmin(), getmax(), obj.getmin(), obj.getmax());
		}

		// box-sphere collision
		if (r == 0 && obj.r != 0)
		{
			return boxcollidesphere(getmin(), getmax(), obj.pos, obj.r);
		}

		// sphere-box collision
		if (r != 0 && obj.r == 0)
		{
			return boxcollidesphere(obj.getmin(), obj.getmax(), pos, r);
		}

		return false;
	}

	void obj3::containvec(vec3 & t)
	{
		t.x = (t.x > l) ? l : (t.x < -l) ? -l : t.x;
		t.y = (t.y > h) ? h : (t.y < -h) ? -h : t.y;
		t.z = (t.z > w) ? w : (t.z < -w) ? -w : t.z;
	}

	void obj3::docontain(obj3 &obj)
	{
		// obj passed in is the object itself
		if (&obj == this)
		{
			Logger->AddLog("obj3::checkcollide(obj3&) : Error checking contain on self, returning false");
			return;
		}

		// either object doesn't detect collision
		if (!bCollide || !obj.bCollide) return;

		// sphere-sphere contain
		if (r != 0 && obj.r != 0)
		{
			if ((pos - obj.pos).magnitude() <= r)
			{
				containvec(obj.pos);
			}
		}

		// box-box contain
		if (r == 0 && obj.r == 0)
		{
			if (boxcontainbox(getmin(), getmax(), obj.getmin(), obj.getmax()))
			{
				containvec(obj.pos);
			}
		}

		// sphere-box contain
		if (r == 0 && obj.r != 0)
		{
			if (spherecontainbox(obj.getmin(), obj.getmax(), pos, r))
			{
				containvec(obj.pos);
			}
		}

		// box-sphere contain
		if (r != 0 && obj.r == 0)
		{
			if (boxcontainsphere(getmin(), getmax(), obj.pos, obj.r))
			{
				containvec(obj.pos);
			}
		}
	}

	void obj3::docontain(vec3 & vec)
	{
		vec3 min = getmin(); vec3 max = getmax();
		if (vec.x > min.x && vec.x < max.x &&
			vec.y > min.y && vec.y < max.y &&
			vec.z > min.z && vec.z < max.z)
		{
			containvec(vec);
		}
	}

	ostream& operator<< (ostream &os, obj3 &v)
	{
		// output object contents to console in the format: l=1 h=2 w=3 r=4
		os << "l=" << v.l
			<< " h=" << v.h
			<< " w=" << v.w
			<< " r=" << v.r;
		return os;
	}

	_Logger& operator<< (_Logger &logger, obj3 &v)
	{
		// output vector contents to Logger in the format: l=1 h=2 r=4
		*Logger << "l=" << v.l
			<< " h=" << v.h
			<< " w=" << v.w
			<< " r=" << v.r;
		return logger;
	}

} // namespace GeorgeLib